fix(security): enforce strict path normalization to prevent workspace…#2301
fix(security): enforce strict path normalization to prevent workspace…#2301ashishSoni1234 wants to merge 4 commits into
Conversation
|
@ashishSoni1234 is attempting to deploy a commit to the Different AI Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Verified this on Windows. The change is safe and fixes a real Windows correctness issue — but a few things worth nailing down before it lands, since it's a security fix. What I checked
Questions / suggestions:
The Windows case-insensitivity fix is a real improvement regardless. Mainly want to make sure the #2285 escape itself is covered (and tested). |
|
Nice layered approach. Walking through it: One thing worth a look: the case-insensitive compare is gated on Minor: after |
…ession tests for different-ai#2285 ## Summary Address all reviewer feedback from PR different-ai#2301 before merge. ### Changes **NEW: apps/server/src/path-security.ts** - Single source of truth for ormalizeWorkspaceRelativePath and esolveSafeChildPath shared across the server codebase. - Eliminates logic drift between the two previous copies of esolveSafeChildPath (routes/files.ts and extensions/openai-image-generation.ts). - Detailed JSDoc rationale for every design decision (case-insensitive comparison, trailing separator, posix.normalize pre-pass, symlink note). **NEW: apps/server/src/path-security.test.ts** - 26 regression + unit tests that directly reproduce the three escape vectors from issue different-ai#2285: - [different-ai#2285-a] raw .. traversal (bare, prefix, workspace/../, deeply nested, Windows backslash form) - [different-ai#2285-b] case-mismatch false-rejection on Windows / macOS - [different-ai#2285-c] sibling-workspace-prefix bypass (/ws vs /ws-evil) - All 26 tests pass on both POSIX and Windows. **MODIFY: apps/server/src/routes/files.ts** - Remove local implementations of both security primitives. - Import from ../path-security.js (the canonical source). - Re-export ormalizeWorkspaceRelativePath for backward compatibility with callers that import from routes/files.ts or server.ts. **MODIFY: apps/server/src/extensions/openai-image-generation.ts** - Remove duplicate esolveSafeChildPath implementation (was still on the old case-sensitive logic — the exact drift Reviewer 1 flagged). - Import from ../path-security.js; logic is now identical to files.ts. ### Reviewer concerns addressed | Concern (Abhijeet1005 / Pablosinyores) | Resolution | |---|---| | No regression test for different-ai#2285 escape | 26 tests in path-security.test.ts | | Duplicate resolveSafeChildPath, drifting logic | Shared module, one copy | | macOS case-insensitivity (darwin) missing | darwin added alongside win32 | | posix.normalize belt-and-suspenders comment | Documented in JSDoc | | Symlink note | Explicitly documented as out-of-scope | Closes different-ai#2285
|
@Abhijeet1005 @Pablosinyores — Thanks for the thorough reviews! Addressed all the points in the latest commit (a77838e): @Abhijeet1005 — your concerns: ✅ Regression tests added — path-security.test.ts has 26 tests directly reproducing all 3 escape vectors from #2285: raw .. traversal (including Windows ....\ form), deeply nested collapsed traversal (a/b/../../../secret), case-mismatch false-rejection on Windows/macOS, and the sibling-workspace-prefix bypass (/ws vs /ws-evil) ✅ macOS (darwin) case-insensitivity — already addressed in commit 2, now lives in the shared module so it's enforced consistently everywhere |
|
Thanks for the fast turnaround — the refactor addresses everything cleanly. Re-verified on Windows:
One thing on the tests: the To actually lock in the case-folding fix, the candidate has to differ in case from the root — e.g. an absolute child: So something like Everything else looks good to me — just that one test to make the case-insensitivity coverage real. |
…tually exercise case-insensitive guard The previous test called resolveSafeChildPath(upperRoot, 'notes.md') with a relative child. Since resolve() builds the candidate FROM upperRoot, both root and candidate shared the same casing — so the old case-sensitive startsWith() check passed trivially, making the test vacuous (it would pass even on pre-PR code). Fix: supply an ABSOLUTE child path whose casing differs from the root string. With that, the old code (case-sensitive) throws a false-rejection, and the new code (case-insensitive via toLowerCase()) correctly accepts it. Split into two platform-aware tests: - win32: c:\projects\ws vs C:\PROJECTS\WS\notes.md - darwin: /projects/ws vs /Projects/WS/notes.md - linux: correctly rejected (legitimately different path on case-sensitive FS) 27 tests pass (was 26). pnpm --filter ./apps/server typecheck -> clean. Addresses review feedback from @Abhijeet1005.
|
@Abhijeet1005 — Thanks for catching the vacuous test! You were exactly right. |
|
The updated
The absolute-child-with-differing-case approach is the right way to lock in the case-fold guard, and splitting it per-platform (win32/darwin accept, linux reject) is a nice touch. LGTM from my side. |
|
@Abhijeet1005 Thank you so much for the review and LGTM! 🙏 Could you or someone from the core team please authorize the Vercel workflows to run so this PR can be merged? I'm really excited to get my first contribution in! |
I don't have the permission to approve, need to wait for a maintainer |
Summary
Implemented robust path normalization and case-insensitive boundary validations to prevent sub-agents from escaping their assigned workspace environments through path manipulation or OS-level file resolution quirks.
Why
Agents were previously able to manipulate paths or exploit OS path normalization discrepancies (e.g. Windows case-insensitivity or redundant resolution steps) to silently access parent directories (e.g., project root) out of their designated local workspace scopes.
Issue
Scope
apps/server/src/routes/files.ts(Core path resolution APIs)Out of scope
Testing
Ran
Result
CI status
Manual verification
explore/readrequests utilizing absolute payload mappings pointing to parent levels.400: Path traversal is not allowedsecurely.Evidence
Risk
posix.normalizesafely without altering functional valid internal paths.Rollback
routes/files.tsif internal valid plugins face pathing complications.